/*
 * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.corba.se.impl.presentation.rmi;

import java.io.Serializable;
import java.io.Externalizable;

import javax.rmi.PortableRemoteObject;
import javax.rmi.CORBA.Util;

import java.rmi.RemoteException;
import java.rmi.UnexpectedException;

import org.omg.CORBA.UserException;

import org.omg.CORBA_2_3.portable.InputStream;
import org.omg.CORBA_2_3.portable.OutputStream;
import org.omg.CORBA.portable.ApplicationException;

import java.lang.reflect.Method;

import com.sun.corba.se.spi.logging.CORBALogDomains;
import com.sun.corba.se.impl.logging.ORBUtilSystemException;

public class ExceptionHandlerImpl implements ExceptionHandler {

  private ExceptionRW[] rws;

  private final ORBUtilSystemException wrapper;

///////////////////////////////////////////////////////////////////////////////
// ExceptionRW interface and implementations.
// Used to read and write exceptions.
///////////////////////////////////////////////////////////////////////////////

  public interface ExceptionRW {

    Class getExceptionClass();

    String getId();

    void write(OutputStream os, Exception ex);

    Exception read(InputStream is);
  }

  public abstract class ExceptionRWBase implements ExceptionRW {

    private Class cls;
    private String id;

    public ExceptionRWBase(Class cls) {
      this.cls = cls;
    }

    public Class getExceptionClass() {
      return cls;
    }

    public String getId() {
      return id;
    }

    void setId(String id) {
      this.id = id;
    }
  }

  public class ExceptionRWIDLImpl extends ExceptionRWBase {

    private Method readMethod;
    private Method writeMethod;

    public ExceptionRWIDLImpl(Class cls) {
      super(cls);

      String helperName = cls.getName() + "Helper";
      ClassLoader loader = cls.getClassLoader();
      Class helperClass;

      try {
        helperClass = Class.forName(helperName, true, loader);
        Method idMethod = helperClass.getDeclaredMethod("id", (Class[]) null);
        setId((String) idMethod.invoke(null, (Object[]) null));
      } catch (Exception ex) {
        throw wrapper.badHelperIdMethod(ex, helperName);
      }

      try {
        Class[] argTypes = new Class[]{
            org.omg.CORBA.portable.OutputStream.class, cls};
        writeMethod = helperClass.getDeclaredMethod("write",
            argTypes);
      } catch (Exception ex) {
        throw wrapper.badHelperWriteMethod(ex, helperName);
      }

      try {
        Class[] argTypes = new Class[]{
            org.omg.CORBA.portable.InputStream.class};
        readMethod = helperClass.getDeclaredMethod("read", argTypes);
      } catch (Exception ex) {
        throw wrapper.badHelperReadMethod(ex, helperName);
      }
    }

    public void write(OutputStream os, Exception ex) {
      try {
        Object[] args = new Object[]{os, ex};
        writeMethod.invoke(null, args);
      } catch (Exception exc) {
        throw wrapper.badHelperWriteMethod(exc,
            writeMethod.getDeclaringClass().getName());
      }
    }

    public Exception read(InputStream is) {
      try {
        Object[] args = new Object[]{is};
        return (Exception) readMethod.invoke(null, args);
      } catch (Exception ex) {
        throw wrapper.badHelperReadMethod(ex,
            readMethod.getDeclaringClass().getName());
      }
    }
  }

  public class ExceptionRWRMIImpl extends ExceptionRWBase {

    public ExceptionRWRMIImpl(Class cls) {
      super(cls);
      setId(IDLNameTranslatorImpl.getExceptionId(cls));
    }

    public void write(OutputStream os, Exception ex) {
      os.write_string(getId());
      os.write_value(ex, getExceptionClass());
    }

    public Exception read(InputStream is) {
      is.read_string(); // read and ignore!
      return (Exception) is.read_value(getExceptionClass());
    }
  }

///////////////////////////////////////////////////////////////////////////////

  public ExceptionHandlerImpl(Class[] exceptions) {
    wrapper = ORBUtilSystemException.get(
        CORBALogDomains.RPC_PRESENTATION);

    int count = 0;
    for (int ctr = 0; ctr < exceptions.length; ctr++) {
      Class cls = exceptions[ctr];
      if (!RemoteException.class.isAssignableFrom(cls)) {
        count++;
      }
    }

    rws = new ExceptionRW[count];

    int index = 0;
    for (int ctr = 0; ctr < exceptions.length; ctr++) {
      Class cls = exceptions[ctr];
      if (!RemoteException.class.isAssignableFrom(cls)) {
        ExceptionRW erw = null;
        if (UserException.class.isAssignableFrom(cls)) {
          erw = new ExceptionRWIDLImpl(cls);
        } else {
          erw = new ExceptionRWRMIImpl(cls);
        }

                /* The following check is not performed
                 * in order to maintain compatibility with
                 * rmic.  See bug 4989312.

                // Check for duplicate repository ID
                String repositoryId = erw.getId() ;
                int duplicateIndex = findDeclaredException( repositoryId ) ;
                if (duplicateIndex > 0) {
                    ExceptionRW duprw = rws[duplicateIndex] ;
                    String firstClassName =
                        erw.getExceptionClass().getName() ;
                    String secondClassName =
                        duprw.getExceptionClass().getName() ;
                    throw wrapper.duplicateExceptionRepositoryId(
                        firstClassName, secondClassName, repositoryId ) ;
                }

                */

        rws[index++] = erw;
      }
    }
  }

  private int findDeclaredException(Class cls) {
    for (int ctr = 0; ctr < rws.length; ctr++) {
      Class next = rws[ctr].getExceptionClass();
      if (next.isAssignableFrom(cls)) {
        return ctr;
      }
    }

    return -1;
  }

  private int findDeclaredException(String repositoryId) {
    for (int ctr = 0; ctr < rws.length; ctr++) {
      // This may occur when rws has not been fully
      // populated, in which case the search should just fail.
      if (rws[ctr] == null) {
        return -1;
      }

      String rid = rws[ctr].getId();
      if (repositoryId.equals(rid)) {
        return ctr;
      }
    }

    return -1;
  }

  public boolean isDeclaredException(Class cls) {
    return findDeclaredException(cls) >= 0;
  }

  public void writeException(OutputStream os, Exception ex) {
    int index = findDeclaredException(ex.getClass());
    if (index < 0) {
      throw wrapper.writeUndeclaredException(ex,
          ex.getClass().getName());
    }

    rws[index].write(os, ex);
  }

  public Exception readException(ApplicationException ae) {
    // Note that the exception ID is present in both ae
    // and in the input stream from ae.  The exception
    // reader must actually read the exception ID from
    // the stream.
    InputStream is = (InputStream) ae.getInputStream();
    String excName = ae.getId();
    int index = findDeclaredException(excName);
    if (index < 0) {
      excName = is.read_string();
      Exception res = new UnexpectedException(excName);
      res.initCause(ae);
      return res;
    }

    return rws[index].read(is);
  }

  // This is here just for the dynamicrmiiiop test
  public ExceptionRW getRMIExceptionRW(Class cls) {
    return new ExceptionRWRMIImpl(cls);
  }
}
